package br.com.hs.nfe.common.util;

import br.com.hs.nfe.common.app.AppConfig;
import br.com.hs.nfe.common.enumeration.SituacaoNFeEnum;
import br.com.hs.nfe.common.enumeration.WSStatusRetornoEnum;
import br.com.hs.nfe.common.exception.HSCommonException;
import br.com.hs.nfe.common.to.LoteTO;
import br.com.hs.nfe.common.to.NotaFiscalTO;
import br.inf.portalfiscal.nfe.ConsReciNFeDocument;
import br.inf.portalfiscal.nfe.ConsStatServDocument;
import br.inf.portalfiscal.nfe.EnviNFeDocument;
import br.inf.portalfiscal.nfe.NFeDocument;
import br.inf.portalfiscal.nfe.RetConsReciNFeDocument;
import br.inf.portalfiscal.nfe.RetConsStatServDocument;
import br.inf.portalfiscal.nfe.RetEnviNFeDocument;
import br.inf.portalfiscal.nfe.TAmb;
import br.inf.portalfiscal.nfe.TCodUfIBGE;
import br.inf.portalfiscal.nfe.TConsReciNFe;
import br.inf.portalfiscal.nfe.TConsStatServ;
import br.inf.portalfiscal.nfe.TEnviNFe;
import br.inf.portalfiscal.nfe.TNFe;
import java.io.UnsupportedEncodingException;
import java.text.MessageFormat;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.axiom.om.util.Base64;

/**
 * Classe responsável por efetuar operações com o XML.
 * @author Ranlive Hrysyk
 */
public class DocumentHelper {

    private static final Pattern REGEX_CODIGO_NUMERICO = Pattern.compile("(.*)\\sId\\s*=\\s*\"NFe\\d{44}\"\\s*(.*)<cNF.*</cNF\\s*>(.*)<cDV.*</cDV\\s*>(.*)", 8);

    /**
     * Obtém a String do XML.
     * @param document Documento XML
     * @param xmlDeclaration Adicionar o cabeçalho do XML
     * @return Retorna a String do XML
     * @throws UnsupportedEncodingException Pode lançar um erro de Encoding.
     */
    public static String createDocumentString(Object document, boolean xmlDeclaration) throws UnsupportedEncodingException {
        XmlOptions xmlOptions = new XmlOptions();
        xmlOptions.setUseDefaultNamespace();
        xmlOptions.setSaveAggressiveNamespaces();
        return xmlDeclaration ? new StringBuilder().append(HSConstants.XML_CABECALHO).append(((XmlObject) document).xmlText(xmlOptions)).toString() : ((XmlObject) document).xmlText(xmlOptions);
    }

    /**
     * Obtém o documento XML da NF-e.
     */
    public static NFeDocument createNFeDocument(String xml) throws XmlException {
        if (xml != null) {
            return NFeDocument.Factory.parse(xml);
        }
        return null;
    }

    /**
     * Remove a declaraclaração namespace do XML.1
     * @param xml Documento XML
     * @param prefix Prefixo do namespace
     */
    public static void removerNamespaceDeclaration(XmlObject xml, String prefix) {
        XmlCursor c = xml.newCursor();
        c.toNextToken();
        while (c.hasNextToken()) {
            if ((c.isNamespace()) && (c.getName() != null) && (prefix.equals(c.getName().getLocalPart()))) {
                c.removeXml();
            }
            c.toNextToken();
        }
        c.dispose();
    }

    /**
     * Remove a declaração XML.
     * @param xml Documetno XML
     * @return XML de retorno
     */
    public static String removerDeclaracaoXML(String xml) {
        int posicaoInicialXML = xml.indexOf("<?xml");
        posicaoInicialXML = posicaoInicialXML != -1 ? xml.indexOf(">") + 1 : 0;
        return xml.substring(posicaoInicialXML);
    }

    /**
     * Obtém o digest value da NF-e.
     * @param notaFiscalTO Nota Fiscal
     * @return  String com o valor do digest value.
     */
    public static String getDigestValue(NotaFiscalTO notaFiscalTO) {
        String digestValue = null;
        try {
            NFeDocument nfe = createNFeDocument(notaFiscalTO.getXmlString());

            if ((nfe != null) && (nfe.getNFe().getSignature() != null)) {
                digestValue = Base64.encode(nfe.getNFe().getSignature().getSignedInfo().getReference().getDigestValue());
            }
        } catch (Exception ex) {
        }
        return digestValue;
    }

    /**
     * Atualiza os dados da nota fiscal.
     * @param notaFiscalTO NotaFiscal a ser atualizada.
     * @throws HSCommonException Pode gerar exceção.
     */
    public static void updateXml(NotaFiscalTO notaFiscalTO) throws HSCommonException {
        if (notaFiscalTO.getSituacao().equals(SituacaoNFeEnum.EM_DIGITACAO)) {
            try {
                NFeDocument doc = DocumentHelper.createNFeDocument(notaFiscalTO.getXmlString()) != null ? DocumentHelper.createNFeDocument(notaFiscalTO.getXmlString()) : NFeDocument.Factory.newInstance();
                TNFe nfe = doc.getNFe() != null ? doc.getNFe() : doc.addNewNFe();
                TNFe.InfNFe inf = nfe.getInfNFe() != null ? nfe.getInfNFe() : nfe.addNewInfNFe();

                //Atribui a versão
                inf.setVersao(notaFiscalTO.getVersao());
                //Atribui os dados do nó Ide
                inf.setIde(DocumentConverter.createIde(notaFiscalTO));
                //Atribui os dados do emitente
                inf.setEmit(DocumentConverter.createEmit(notaFiscalTO.getEmitenteTO()));
                //Atribui os dados de retirada
                if (notaFiscalTO.getRetirada() != null) {
                    inf.setRetirada(DocumentConverter.createTLocal(notaFiscalTO.getRetirada()));
                } else if (inf.isSetRetirada()) {
                    inf.unsetRetirada();
                }
                //Atribui os dados do cliente
                inf.setDest(DocumentConverter.createDest(notaFiscalTO.getClienteTO()));
                //Atribui os dados de entrega
                if (notaFiscalTO.getEntrega() != null) {
                    inf.setEntrega(DocumentConverter.createTLocal(notaFiscalTO.getEntrega()));
                } else if (inf.isSetEntrega()) {
                    inf.unsetEntrega();
                }
                //Atribui os dados dos produtos
                inf.setDetArray(DocumentConverter.createDetArray(notaFiscalTO.getListaProdutos()));
                //Atribui os dados do transporte
                inf.setTransp(DocumentConverter.createTransp(notaFiscalTO.getTransporteTO()));
                //Atribui os dados da informações adicionais
                if (notaFiscalTO.getInfoTO() != null) {
                    inf.setInfAdic(DocumentConverter.createInfAdic(notaFiscalTO.getInfoTO()));
                } else if (inf.isSetInfAdic()) {
                    inf.unsetInfAdic();
                }

                //Atualiza o XML
                notaFiscalTO.setXmlString(DocumentHelper.createDocumentString(doc, true));
            } catch (Exception ex) {
                throw new HSCommonException(ex.getMessage(), ex);
            }
        } else {
        }
    }

    /**
     * Exclui o código numérico do XML da NF-e.
     * @param notaFiscalTO 
     */
    public static void excluirCodigoNumerico(NotaFiscalTO notaFiscalTO) {
        Matcher matcher = REGEX_CODIGO_NUMERICO.matcher(notaFiscalTO.getXmlString());
        if ((matcher.find()) && (matcher.groupCount() == 4)) {
            String antesId = matcher.group(1);
            String depoisId = matcher.group(2);
            if ((Pattern.matches("[a-zA-Z].*", depoisId)) && (!antesId.endsWith(" "))) {
                antesId = new StringBuilder().append(antesId).append(" ").toString();
            }
            notaFiscalTO.setXmlString(new StringBuilder().append(antesId).append(depoisId).append(matcher.group(3)).append(matcher.group(4)).toString());
        }
    }

    /**
     * Cria o documento XML de consulta status do serviço.
     * @param codigoUfConsulta Código da UF do serviço
     */
    public static String createConsStatServDocument(String codigoUfConsulta) throws UnsupportedEncodingException, HSCommonException {
        ConsStatServDocument consStatServDocument = ConsStatServDocument.Factory.newInstance();
        TConsStatServ consStatServ = consStatServDocument.addNewConsStatServ();
        consStatServ.setCUF(TCodUfIBGE.Enum.forString(codigoUfConsulta));
        consStatServ.setTpAmb(TAmb.Enum.forString(AppConfig.getInstance().getTipoAmbiente()));
        consStatServ.setVersao(AppConfig.getInstance().getVersaoSchemaStatusServico());
        consStatServ.setXServ(TConsStatServ.XServ.STATUS);
        return createDocumentString(consStatServDocument, true);
    }

    /**
     * Valida o retorno do XML de consulta status do serviço.
     * @param consultaStatusRetornoXML XML de Retorno
     * @return Status do Web Service
     */
    public static WSStatusRetornoEnum validateRetConsStatServDocument(String consultaStatusRetornoXML) {
        RetConsStatServDocument document = null;
        try {
            document = RetConsStatServDocument.Factory.parse(consultaStatusRetornoXML);
        } catch (XmlException e) {
            return WSStatusRetornoEnum.XML_INVALIDO;
        }
        if (!document.validate()) {
            return WSStatusRetornoEnum.XML_INVALIDO;
        }
        if (document.getRetConsStatServ().getCStat().equals(HSConstants.WS_CODIGO_SERVICO_OPERACAO)) {
            return WSStatusRetornoEnum.STATUS_SERVICO_EM_OPERACAO;
        }
        if (document.getRetConsStatServ().getCStat().equals(HSConstants.WS_CODIGO_SERVICO_EM_PROCESSO_DE_DESATIVACAO)) {
            return WSStatusRetornoEnum.STATUS_SERVICO_EM_DESATIVACAO;
        }
        if (document.getRetConsStatServ().getCStat().equals(HSConstants.WS_CODIGO_SERVICO_PARALISADO_PARA_AQUELA_UF)) {
            return WSStatusRetornoEnum.STATUS_SERVICO_PARALISADO;
        }
        return WSStatusRetornoEnum.NOK;
    }

    /**
     * Cria o documento XML de envio da NF-e.
     * @param loteTO Lote do processamento.
     */
    public static String createEnviNFeDocument(LoteTO loteTO) throws HSCommonException {
        StringBuilder bufferNotaFiscal = null;
        try {
            EnviNFeDocument enviNFeDocument = EnviNFeDocument.Factory.newInstance();
            TEnviNFe enviNFe = enviNFeDocument.addNewEnviNFe();
            enviNFe.setIdLote(Long.toString(loteTO.getIdLote()));
            enviNFe.setVersao(AppConfig.getInstance().getVersaoSchemaEnvioNFe());
            bufferNotaFiscal = new StringBuilder(createDocumentString(enviNFeDocument, true));
            for (NotaFiscalTO notaFiscalTO : loteTO.getNotasFiscais()) {
                bufferNotaFiscal.insert(bufferNotaFiscal.lastIndexOf("</"), removerDeclaracaoXML(notaFiscalTO.getXmlString()));
            }
        } catch (Exception e) {
            throw new HSCommonException(e.getMessage(), e);
        }
        return bufferNotaFiscal.toString();
    }

    /**
     * Valida o retorno do XML de envio da NF-e.
     * @param retornoEnvio XML de Retorno
     * @param loteTO Lote de envio
     * @return Status do retorno
     */
    public static WSStatusRetornoEnum validateRetEnviNFeDocument(String retornoEnvio, LoteTO loteTO) {
        if (retornoEnvio == null) {
            return WSStatusRetornoEnum.XML_INVALIDO;
        }
        RetEnviNFeDocument document = null;
        try {
            document = RetEnviNFeDocument.Factory.parse(retornoEnvio);
        } catch (XmlException e) {
            return WSStatusRetornoEnum.XML_INVALIDO;
        }
        if (!document.validate()) {
            return WSStatusRetornoEnum.XML_INVALIDO;
        }
        if (loteTO.getCodigoRetorno().equals(HSConstants.WS_CODIGO_LOTE_RECEBIDO)) {
            loteTO.setCodigoRetorno(document.getRetEnviNFe().getCStat());
            loteTO.setNumeroRecibo(document.getRetEnviNFe().getInfRec().getNRec());
            for (NotaFiscalTO notaFiscalTO : loteTO.getNotasFiscais()) {
                notaFiscalTO.addMessage(new StringBuilder().append(HSMessageConstants.MSG_NOTA_ENVIADA_OK).append(" ").append(MessageFormat.format(HSMessageConstants.MSG_NUMERO_RECIBO_X, new Object[]{loteTO.getNumeroRecibo()})).toString());
            }
            return WSStatusRetornoEnum.OK;
        }
        String codigoErro = document.getRetEnviNFe().getCStat();
        String mensagemErro = document.getRetEnviNFe().getXMotivo();
        String motivo = document.getRetEnviNFe().getXMotivo();
        for (NotaFiscalTO notaFiscalTO : loteTO.getNotasFiscais()) {
            notaFiscalTO.setCodigoErro(codigoErro);
            notaFiscalTO.setMensagemErro(mensagemErro);
            notaFiscalTO.addError(motivo);
        }
        return WSStatusRetornoEnum.NOK;
    }

    /**
     * Cria o documento XML de consulta recibo da NF-e.
     * @param numeroRecibo Número do recibo
     */
    public static String createConsReciNFeDocument(String numeroRecibo) throws HSCommonException {
        try {
            ConsReciNFeDocument consReciNFeDocument = ConsReciNFeDocument.Factory.newInstance();
            TConsReciNFe consReciNFe = consReciNFeDocument.addNewConsReciNFe();
            consReciNFe.setVersao(AppConfig.getInstance().getVersaoSchemaConsRecibo());
            consReciNFe.setTpAmb(TAmb.Enum.forString(AppConfig.getInstance().getTipoAmbiente()));
            consReciNFe.setNRec(numeroRecibo);
            return createDocumentString(consReciNFeDocument, true);
        } catch (Exception ex) {
            throw new HSCommonException(ex.getMessage(), ex);
        }
    }
    
    /**
     * Valida o retorno do XML de consulta recibo.
     * @param buscaRetornoXML XML de retorno
     * @param loteTO Lote de envio
     * @return Status do retorno
     */
    public static WSStatusRetornoEnum validateRetConsReciNFeDocument(String buscaRetornoXML, LoteTO loteTO) {
        RetConsReciNFeDocument document = null;
        try {
            document = RetConsReciNFeDocument.Factory.parse(buscaRetornoXML);
        } catch (XmlException e) {
            return WSStatusRetornoEnum.XML_INVALIDO;
        }
        if (!document.validate()) {
            return WSStatusRetornoEnum.XML_INVALIDO;
        }
        String cStat = document.getRetConsReciNFe().getCStat();
        List protNFeList = XMLHelper.getTagConteudo(buscaRetornoXML, "protNFe", true);
        boolean hasProtNFe = (protNFeList != null) && (!protNFeList.isEmpty());
        if ((HSConstants.WS_CODIGO_LOTE_NAO_LOCALIZADO.equals(cStat)) || ((HSConstants.WS_CODIGO_LOTE_PROCESSADO.equals(cStat)) && (!hasProtNFe))) {
            return WSStatusRetornoEnum.NOK;
        }
        loteTO.setCodigoRetorno(cStat);
        loteTO.addError((String) XMLHelper.getTagConteudo(buscaRetornoXML, "xMotivo", false).get(0));
        return WSStatusRetornoEnum.OK;
    }
}
